// Copyright (C) 2024 Shpz<shaaapz@gmail.com>. All Rights Reserved.

/**
 * 弹簧臂组件。
 * 
 * 用于控制第三人称摄像机跟随目标，避免摄像机穿入环境物体中。
 * 
 * 目前实现了弹簧臂功能，后续加入摄像机延迟跟随。
 */

import { CCFloat, CCInteger, Component, Node, PhysicsSystem, Vec3, _decorator, geometry, v3, Quat, Mat4, mat4, clamp, quat } from 'cc';
import { Pawn } from './Pawn';

const { ccclass, property, executeInEditMode, menu, disallowMultiple } = _decorator;

@ccclass('SpringArmComponent')
@executeInEditMode
@menu("SpringArm/SpringArm")
@disallowMultiple
export class SpringArmComponent extends Component {
    /**
     * 摄像机跟随的目标
     */
    @property({
        type: Node,
        tooltip: "跟随的目标"
    })
    public target: Node = null;

    /**
     * 摄像机节点
     */
    @property({
        type: Node,
        tooltip: "摄像机节点"
    })
    public camera: Node = null;

    /**
     * 是否开启摄像机滞后。
     * 
     * 开启则摄像机会滞后于目标位置，以平滑摄像机移动。
     */
    @property({
        tooltip: `是否开启摄像机滞后。
        开启则摄像机会滞后于目标位置，以平滑摄像机移动。`
    })
    public enableCameraLag = false;

    /**
     * 是否开启摄像机旋转滞后。
     * 
     * 开启则摄像机会滞后于目标旋转，以平滑摄像机旋转。
     */
    @property({
        tooltip: `是否开启摄像机旋转滞后。
        开启则摄像机会滞后于目标旋转，以平滑摄像机旋转。`
    })
    public enableCameraRotationLag = false;

    /**
     * 如果开启摄像机滞后，则控制摄像机达到目标位置的速度。
     * 
     * 速度值越低表示移动速度越慢（滞后大），值越高表示移动速度越快（滞后小），值为零表示立即移动（无滞后）。
     */
    @property({
        type: CCFloat,
        tooltip: `如果开启摄像机滞后，则控制摄像机达到目标位置的速度。
        速度值越低表示移动速度越慢（滞后大），值越高表示移动速度越快（滞后小），值为零表示立即移动（无滞后）。`,
        range: [0, 1000]
    })
    public cameraLagSpeed = 10;

    /**
     * 如果开启摄像机旋转滞后，则控制摄像机达到目标旋转的速度。
     * 
     * 速度值越低表示旋转速度越慢（滞后大），值越高表示旋转速度越快（滞后小），值为零表示立即旋转（无滞后）。
     */
    @property({
        type: CCFloat,
        tooltip: `如果开启摄像机旋转滞后，则控制摄像机达到目标旋转的速度。
        速度值越低表示旋转速度越慢（滞后大），值越高表示旋转速度越快（滞后小），值为零表示立即旋转（无滞后）`,
        range: [0, 1000]
    })
    public cameraRotationLagSpeed = 1;

    /**
     * 摄像机目标可能滞后于当前位置的最大距离。
     * 
     * 如果设置为零，则不计算最大滞后距离。
     */
    @property({
        type: CCFloat,
        tooltip: `摄像机目标可能滞后于当前位置的最大距离。
        如果设置为零，则不计算最大滞后距离。`,
        min: 0
    })
    public cameraLagMaxDistance = 0;

    /**
     * 检测探头大小
     */
    @property({
        type: CCFloat,
        tooltip: "检测探头大小"
    })
    public probeSize = 0.3;

    /**
     * 弹簧臂初始长度
     */
    @property({
        type: CCFloat,
        tooltip: "弹簧臂初始长度",
        min: 0
    })
    public armLength = 3;

    /**
     * 插槽偏移，摄像机侧
     */
    @property({
        tooltip: "插槽偏移，摄像机侧"
    })
    public socketOffset = new Vec3();

    /**
     * 目标偏移，目标侧
     */
    @property({
        tooltip: "目标偏移，目标侧"
    })
    public targetOffset = new Vec3();

    /**
     * 如果开启碰撞测试，
     * 
     * 则使用 collisionLayerMask 和 probeSize 执行碰撞测试，以防止摄像机穿透到场景中
     */
    @property({
        tooltip: `是否做碰撞测试,
        则使用 collisionLayerMask 和 probeSize 执行碰撞测试，以防止摄像机穿透到场景中`
    })
    public doCollisionTest = true;

    /**
     * 碰撞测试遮罩
     */
    @property({
        type: CCInteger,
        tooltip: "碰撞测试遮罩"
    })
    public collisionLayerMask = 0;

    /**
     * 是否使用目标的旋转
     */
    @property({
        tooltip: "是否使用目标的旋转"
    })
    public useTargetRotation = true;

    /**
     * 世界坐标系下目标偏移的位置
     */
    public targetOffsetPosition = new Vec3();

    /**
     * 世界坐标系下射线方向
     */
    public raycastDirection = new Vec3();

    /**
     * 世界坐标系下插槽偏移的位置
     */
    public socketOffsetPosition = new Vec3();

    protected previousDesiredRot = quat();

    start() {

    }

    update(deltaTime: number) {
        if (!this.target) {
            return;
        }

        this.updateTransform();

        if (this.doCollisionTest) {
            this.checkCollision();
        }

        // if (this.useTargetRotation) {
        this.followRotate(deltaTime);
        // }

        this.followMovement(deltaTime);
    }

    onEnable() {
        this.initialCamera();
        this.updateTransform();
    }

    protected initialCamera() {
        this.camera.setWorldRotation(this.node.worldRotation);
        this.camera.setWorldPosition(this.node.worldPosition);
    }

    /**
     * 更新偏移位置
     */
    protected updateTransform() {
        const trans = this.target;
        const rotation = Mat4.fromQuat(mat4(), trans.worldRotation);

        // 计算世界坐标系下目标偏移的位置
        let worldTargetOffset = Vec3.transformMat4(v3(), this.targetOffset, rotation);
        this.targetOffsetPosition = Vec3.add(v3(), trans.worldPosition, worldTargetOffset);

        // 计算插槽偏移位置: 
        // v3_1: 目标当前的正后方作为射线方向
        // v3_2: 射线方向 * 弹簧臂长度 = 弹簧臂尺寸
        // v3_3: 目标偏移点作为起点，弹簧臂起点 + 弹簧臂尺寸 = 弹簧臂终点
        // 最后加上插槽偏移得到插槽偏移位置
        let v3_1 = Vec3.negate(v3(), trans.forward);
        let v3_2 = Vec3.multiplyScalar(v3(), v3_1, this.armLength);
        let v3_3 = Vec3.add(v3(), v3_2, this.targetOffsetPosition);

        let worldSocketOffset = Vec3.transformMat4(v3(), this.socketOffset, rotation);
        this.socketOffsetPosition = Vec3.add(v3(), v3_3, worldSocketOffset);

        // 计算加上偏移后的射线方向
        this.raycastDirection = Vec3.subtract(
            v3(),
            this.socketOffsetPosition,
            this.targetOffsetPosition
        ).normalize();
    }

    /**
     * 射线碰撞检测
     */
    protected checkCollision() {
        const phySys = PhysicsSystem.instance;

        let sweepRay = geometry.Ray.create(
            this.targetOffsetPosition.x,
            this.targetOffsetPosition.y,
            this.targetOffsetPosition.z,
            this.raycastDirection.x,
            this.raycastDirection.y,
            this.raycastDirection.z
        );

        let isSweep = phySys.sweepSphereClosest(
            sweepRay,
            this.probeSize,
            this.collisionLayerMask,
            this.armLength,
            false
        );

        if (isSweep) {
            let ray = geometry.Ray.create(
                this.targetOffsetPosition.x,
                this.targetOffsetPosition.y,
                this.targetOffsetPosition.z,
                this.raycastDirection.x,
                this.raycastDirection.y,
                this.raycastDirection.z
            );

            if (phySys.raycastClosest(
                ray,
                this.collisionLayerMask,
                this.armLength,
                false
            )) {
                this.socketOffsetPosition = phySys.raycastClosestResult.hitPoint;
            }
        }
    }

    /**
     * 跟随目标旋转。
     * 
     * 如果开启了摄像机旋转滞后，则会计算插值。
     * 
     * @param dt  
     */
    protected followRotate(dt: number) {
        let target = this.target.worldRotation.clone();

        if (this.enableCameraRotationLag) {
            Quat.slerp(target, this.camera.worldRotation, target, dt * this.cameraRotationLagSpeed);
        }

        this.camera.setWorldRotation(target);
    }

    /**
     * 跟随目标移动。
     * 
     * 如果开启了摄像机移动滞后，则会计算插值。
     * 
     * @param dt  
     */
    protected followMovement(dt: number) {
        let target = this.socketOffsetPosition.clone();

        if (this.enableCameraLag) {
            let t = clamp(dt * this.cameraLagSpeed, 0, 1);
            Vec3.lerp(target, this.camera.worldPosition, target, t);
        }

        this.camera.setWorldPosition(target);
    }
}